Odin-Caio-Fork with Raylib (2026-04-01)

Setup

  • Install Odin :

    • caioraphael1/Odin

      • There are no releases.

      • The code has to be compiled from source by running the .\build.bat release  in x64 Native Tools Command Prompt for VS 2022  command shell.

  • Get the latest RayLib release :

    • Not needed, as Raylib is already part of the Odin vendor, located at: <odin_installation_folder>/vendor/raylib/ .

  • The file system :

     .
    └──  main.odin
    
    • Inside the <odin_installation_folder>/vendor/raylib :

       .
      ├── 󰂺 README.md
      ├──  raymath.odin
      ├──  raylib.odin
      ├──  raygui.odin
      ├──  LICENSE
      ├──  easings.odin
      ├──  windows
      │   ├──  raylibdll.lib
      │   ├──  raylib.lib
      │   ├──  raylib.dll
      │   ├──  rayguidll.lib
      │   ├──  raygui.lib
      │   └──  raygui.dll
      ├──  wasm        // not needed for Windows
      │   ├──  libraylib.a
      │   └──  libraygui.a
      ├──  rlgl        // standalone module, not required for raylib "A multi-OpenGL abstraction layer with an immediate-mode style API"
      │   └──  rlgl.odin
      ├──  macos-arm64 // not needed for Windows
      │   ├──  libraygui.dylib
      │   └──  libraygui.a
      ├──  macos       // not needed for Windows
      │   ├──  libraylib.dylib
      │   ├──  libraylib.a
      │   ├──  libraylib.550.dylib
      │   ├──  libraylib.5.5.0.dylib
      │   ├──  libraygui.dylib
      │   └──  libraygui.a
      └──  linux       // not needed for Windows
          ├──  libraylib.so.550
          ├──  libraylib.so.5.5.0
          ├──  libraylib.so
          ├──  libraylib.a
          ├──  libraygui.so
          └──  libraygui.a
      
  • Compile and execute :

    • odin run .

Moving Ball

/* 
odin run .
*/

import rl "vendor:raylib"


main :: proc() {
    SCREEN_WIDTH  :: 800
    SCREEN_HEIGHT :: 450

    rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Raylib in Odin-Caio-Fork | 1_moving_ball")
    defer rl.CloseWindow()
    rl.SetTargetFPS(60)

    // Initialize ball
    ball_position := [2]f32{ SCREEN_WIDTH/2.0, SCREEN_HEIGHT/2.0 }

    for !rl.WindowShouldClose() {
        // Update ball position
        if rl.IsKeyDown(.RIGHT) { ball_position.x += 2.0 }
        if rl.IsKeyDown(.LEFT)  { ball_position.x -= 2.0 }
        if rl.IsKeyDown(.UP)    { ball_position.y -= 2.0 }
        if rl.IsKeyDown(.DOWN)  { ball_position.y += 2.0 }

        // Draw
        {
            rl.BeginDrawing()
            defer rl.EndDrawing()
            rl.ClearBackground(rl.WHITE)

            rl.DrawText("move the ball with arrow keys", 10, 10, 20, rl.DARKGRAY)
            rl.DrawCircleV(ball_position, 50, rl.MAROON)
        }
    }
}

Collect The Coins

/* 
odin run .
*/

import rl "vendor:raylib"

MAX_COINS    :: 20
PLAYER_SPEED :: 4.0

Player :: struct {
    position: [2]f32,
    radius:   f32,
    score:    int,
}

Coin :: struct {
    position: [2]f32,
    radius:   f32,
    active:   bool,
}

main :: proc() {
    SCREEN_WIDTH  :: 800
    SCREEN_HEIGHT :: 600

    rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Raylib in Odin-Caio-Fork | 2_collect_the_coins")
    defer rl.CloseWindow()
    rl.SetTargetFPS(60)

    // Initialize player
    player := Player{
        position = { 400, 300 },
        radius   = 20,
        score    = 0,
    }

    // Initialize coins
    coins: [MAX_COINS]Coin
    for i in 0..<MAX_COINS {
        coins[i].position = {
            f32(rl.GetRandomValue(20, SCREEN_WIDTH - 20)),
            f32(rl.GetRandomValue(20, SCREEN_HEIGHT - 20)),
        }
        coins[i].radius = 8
        coins[i].active = true
    }

    for !rl.WindowShouldClose() {
        // Update player
        if rl.IsKeyDown(.W) { player.position.y -= PLAYER_SPEED }
        if rl.IsKeyDown(.S) { player.position.y += PLAYER_SPEED }
        if rl.IsKeyDown(.A) { player.position.x -= PLAYER_SPEED }
        if rl.IsKeyDown(.D) { player.position.x += PLAYER_SPEED }

        // Check collisions
        for i in 0..<MAX_COINS {
            if coins[i].active {
                dist := rl.Vector2Distance(player.position, coins[i].position)

                if dist < player.radius + coins[i].radius {
                    coins[i].active = false
                    player.score += 1
                }
            }
        }

        // Draw
        {
            rl.BeginDrawing()
            defer rl.EndDrawing()
            rl.ClearBackground(rl.DARKGREEN)

            rl.DrawCircleV(player.position, player.radius, rl.BLUE)
            for i in 0..<MAX_COINS {
                if coins[i].active {
                    rl.DrawCircleV(coins[i].position, coins[i].radius, rl.GOLD)
                }
            }

            rl.DrawText(rl.TextFormat("Score: %d", player.score), 10, 10, 20, rl.WHITE)            
        }
    }
}

Space Invaders

import "base:mem"
import "base:mem/allocators"
import "base:container/slice"

import "core:os"
import "core:fmt"

import rl "vendor:raylib"

MAX_BULLETS  :: 64
BULLET_SPEED :: 6.0

Player :: struct {
    position: [2]f32,
    radius:   f32,
}

Bullet :: struct {
    position: [2]f32,
    velocity: [2]f32,
    radius:   f32,
    active:   bool,
}

Enemy :: struct {
    position: [2]f32,
    radius:   f32,
    active:   bool,
    color:    rl.Color,
}

main :: proc() {
    allocator := allocators.heap_allocator()
    os.init_std_files()
    allocators.temp_allocator_init(0, allocator)
    defer allocators.temp_allocator_destroy()

    SCREEN_WIDTH  :: 800
    SCREEN_HEIGHT :: 600

    rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Raylib in Odin-Caio-Fork | 3_space_invaders")
    defer rl.CloseWindow()
    rl.SetTargetFPS(60)

    // Initialize player
    player := Player{ 
        { 400, 500 }, 
        20,
    } 
    bullets: [MAX_BULLETS]Bullet // Stack allocation

    // Initialize enemies
    enemies, slice_err := slice.create(Enemy, 20, allocator) // Heap allocation, using context.allocator implicitly
    fmt.assertf(slice_err == nil, "Error '%v' when creating the slice", slice_err)
    defer _ = slice.delete(enemies, allocator)

    enemies_destroyed_count: uint
    spawn_inactive_enemies(enemies)

    for !rl.WindowShouldClose() {
        // Update player
        if rl.IsKeyDown(.A) { player.position.x -= 4 }
        if rl.IsKeyDown(.D) { player.position.x += 4 }
        if rl.IsKeyDown(.W) { player.position.y -= 4 }
        if rl.IsKeyDown(.S) { player.position.y += 4 }
        if rl.IsKeyPressed(.SPACE) {
            spawn_bullet(bullets[:], player.position)
        }

        // Update bullets
        for i in 0..<len(bullets) {
            if bullets[i].active {
                bullets[i].position.x += bullets[i].velocity.x
                bullets[i].position.y += bullets[i].velocity.y

                if bullets[i].position.y < 0 {
                    bullets[i].active = false
                }
            }
        }

        // Update enemies
        active_enemies := 0
        for i in 0..<len(enemies) {
            if enemies[i].active {
                enemies[i].position.y += 0.5
                active_enemies += 1
            }
        }

        // Respawn inactive enemies
        if active_enemies < 5 {
            spawn_inactive_enemies(enemies)
        }

        // Check Collisions
        for i in 0..<len(bullets) {
            if !bullets[i].active { continue }

            for j in 0..<len(enemies) {
                if !enemies[j].active { continue }

                d := rl.Vector2Distance(bullets[i].position, enemies[j].position)
                r := bullets[i].radius + enemies[j].radius

                if d < r {
                    bullets[i].active  = false
                    enemies[j].active  = false
                    enemies_destroyed_count += 1
                }
            }
        }

        // Draw
        {
            rl.BeginDrawing()
            defer rl.EndDrawing()
            rl.ClearBackground(rl.BLACK)

            rl.DrawCircleV(player.position, player.radius, rl.BLUE)

            for i in 0..<len(bullets) {
                if bullets[i].active {
                    rl.DrawCircleV(bullets[i].position, bullets[i].radius, rl.YELLOW)
                }
            }
            for i in 0..<len(enemies) {
                if enemies[i].active {
                    rl.DrawCircleV(enemies[i].position, enemies[i].radius, enemies[i].color)
                }
            }

            rl.DrawText("Space Invaders: WASD move | SPACE shoot", 10, 10, 20, rl.WHITE)

            rl.DrawText(fmt.ctprintf("Enemies destroyed: %v", enemies_destroyed_count), 10, 40, 20, rl.YELLOW)
                // Prints while doing a temporary allocation.
        }

        free_all_err := mem.free_all(allocators.temp_allocator)
        fmt.assertf(free_all_err == nil, "Error '%v' when freeing the temp_allocator", free_all_err)
    }
}

spawn_bullet :: proc(bullets: []Bullet, pos: [2]f32) {
    for i in 0..<len(bullets) {
        if !bullets[i].active {
            bullets[i].active   = true
            bullets[i].position = pos
            bullets[i].velocity = {0, -BULLET_SPEED}
            bullets[i].radius   = 5
            return
        }
    }
}

spawn_inactive_enemies :: proc(enemies: []Enemy) {
    for i in 0..<len(enemies) {
        if !enemies[i].active {
            enemies[i].position = {
                f32(rl.GetRandomValue(50, 750)),
                f32(rl.GetRandomValue(50, 300)),
            }
            enemies[i].radius = 15
            enemies[i].active = true
            enemies[i].color  = {
                u8(rl.GetRandomValue(100, 255)),
                u8(rl.GetRandomValue(100, 255)),
                u8(rl.GetRandomValue(100, 255)),
                255,
            }
        }
    }
}